from django.db import models
from django.db.models.fields import CharField
from django.contrib.auth.models import User
from django.urls.base import reverse
from django.utils import timezone
from django.utils.html import strip_tags
import markdown

# Create your models here.
# 数据模块，用于设计数据库等


# __str__(self)方法，那么就会打印从在这个方法中return的数据
# __str__方法需要返回一个字符串，当做这个对象的描写
class Category(models.Model):
    """
    Django 要求模型必须继承 models.Model 类。
    Category 只需要一个简单的分类名 name 就可以了。
    CharField 指定了分类名 name 的数据类型，CharField 是字符型，
    CharField 的 max_length 参数指定其最大长度，超过这个长度的分类名就不能被存入数据库。
    """

    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name


class Tag(models.Model):
    """
    标签 Tag
    """
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name


class Post(models.Model):

    # 文章标题
    title = models.CharField(max_length=70)

    # 文章内容
    # 存储比较短的字符串可以使用 CharField，但对于文章的正文来说可能会是一大段文本，因此使用 TextField 来存储大段文本
    body = models.TextField()

    # 这两个列分别表示文章的创建时间和最后一次修改时间，存储时间的字段用 DateTimeField 类型。
    created_time = models.DateTimeField()
    modified_time = models.DateTimeField()

    # 文章摘要
    # 默认情况下 CharField 要求我们必须存入数据，否则就会报错。指定 CharField 的 blank=True 参数值后就可以允许空值了。
    excerpt = models.CharField(max_length=200, blank=True)

    # 这是分类与标签，分类与标签的模型已经定义在上面。
    # 把文章对应的数据库表和分类、标签对应的数据库表关联了起来，但是关联形式稍微有点不同。
    # 一篇文章只能对应一个分类，但是一个分类下可以有多篇文章，所以我们使用的是 ForeignKey，即一对多的关联关系。
    # 而对于标签来说，一篇文章可以有多个标签，同一个标签下也可能有多篇文章，所以我们使用 ManyToManyField，表明这是多对多的关联关系。
    # 文章可以没有标签，因此为标签 tags 指定了 blank=True。
    # on_delete=models.CASCADE 删除主表的数据时候从表中的数据也随着一起删除。
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    tags = models.ManyToManyField(Tag, blank=True)

    # 文章作者，这里 User 是从 django.contrib.auth.models 导入的。
    # django.contrib.auth 是 Django 内置的应用，专门用于处理网站用户的注册、登录等流程，User 是 Django 为写好的用户模型。
    # 通过 ForeignKey 把文章和 User 关联了起来。
    # 一篇文章只能有一个作者，而一个作者可能会写多篇文章，因此这是一对多的关联关系，和 Category 类似。
    author = models.ForeignKey(User, on_delete=models.CASCADE)

    # 字段的类型为 PositiveIntegerField，该类型的值只允许为正整数或 0，因为阅读量不可能为负值。
    # 初始化时 views 的值为 0。将 editable 参数设为 False 将不允许通过 django admin 后台编辑此字段的内容
    views = models.PositiveBigIntegerField(default=0, editable=False)

    def __str__(self):
        return self.title

    # models.Model 的子类里定义一个 Meta 的内部类，这个内部类通过指定一些属性来规定这个类该有的一些特性
    # ordering 属性用来指定文章排序方式，['-created_time'] 指定了依据哪个属性的值进行排序，这里指定为按照文章发布时间排序，且负号表示逆序排列
    class Meta:
        ordering = ['-created_time']

    # 自定义 get_absolute_url 方法
    # 记得从 django.urls.base 中导入 reverse 函数
    # #reverse的第一个参数是urls.py的url中的name
    # reverse 函数，它的第一个参数的值是 '应用名:detail'，意思是 GO_lww 应用下的 name=detail 的函数
    # get_absolute_url 函数返回的就是 /post/pk/
    def get_absolute_url(self):
        return reverse('GO_lww:detail', kwargs={'pk': self.pk})

    # # 返回首页url
    # def get_index_url(self):
    #     return reverse('GO_lww:index')

    # 通过覆写模型的 save 方法，在数据被保存到数据库前，先从 body 字段摘取 N 个字符保存到 excerpt 字段中，从而实现自动摘要的目的
    def save(self, *args, **kwargs):
        self.modified_time = timezone.now()

        # 首先实例化一个 Markdown 类，用于渲染 body 的文本。
        # 由于摘要并不需要生成文章目录，所以去掉了目录拓展。
        md = markdown.Markdown(extensions=[
            'markdown.extensions.extra',
            'markdown.extensions.codehilite',
        ])

        if self.excerpt != "":
            self.excerpt = self.excerpt
        else:

            # 先将 Markdown 文本渲染成 HTML 文本
            # strip_tags 去掉 HTML 文本的全部 HTML 标签
            # 从文本摘取前 54 个字符赋给 excerpt
            self.excerpt = strip_tags(md.convert(self.body))[:54]
        return super().save(*args, **kwargs)

    # increase_views 方法首先将自身对应的 views 字段的值 +1（此时数据库中的值还没变），然后调用 save 方法将更改后的值保存到数据库。
    # 使用了 update_fields 参数来告诉 Django 只更新数据库中 views 字段的值
    def increase_views(self):
        self.views += 1
        self.save(update_fields=['views'])
